/*
 * This file is part of KillCount, which is a GameScript for OpenTTD
 * Copyright (C) 2012-2013  Leif Linse
 *
 * KillCount is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by
 * the Free Software Foundation; version 2 of the License
 *
 * KillCount is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with KillCount; If not, see <http://www.gnu.org/licenses/> or
 * write to the Free Software Foundation, Inc., 51 Franklin Street,
 * Fifth Floor, Boston, MA 02110-1301 USA.
 *
 */

/** Import source code files **/
require("version.nut"); // get SELF_VERSION
require("scoretable.nut");

class KillCount extends GSController
{
	_loaded_data               = null;
	_score_table               = null;
	_score_board_page          = null;
	_score_board_page_elements = null;
	_init_done                 = null;

	constructor()
	{
		this._loaded_data               = null;
		this._score_table               = scoreTableCreate();
		this._score_board_page          = null; //StoryPage ID
		this._score_board_page_elements = [];   //StoryPageElement IDs
		this._init_done                 = false;
	}
}

/*
 * Start() contains of two main parts. First initialization (which is
 * located in Init), and then the main loop.
 */
function KillCount::Start()
{
	GSLog.Info("Calling Init");
	this.Init();

	// Wait for the game to start (or more correctly, tell OpenTTD to not
	// execute our GS further in world generation)
	GSController.Sleep(1);

	// Main Game Script loop
	GSLog.Info("Starting game loop");
	while (true) {
		local loop_start_tick = GSController.GetTick();

		// Handle incoming messages from OpenTTD
		this.HandleEvents();

		// Loop with a frequency of one day
		local ticks_used = GSController.GetTick() - loop_start_tick;
		GSController.Sleep(max(1, 74 - ticks_used));
	}
}

/*
 * Initialise values either from loading a save or from scratch
 */
function KillCount::Init()
{
	if (this._loaded_data != null) {
		GSLog.Info("Loading data");
		this._score_table               = this._loaded_data["score_table"];
		this._score_board_page          = this._loaded_data["score_board_page"];
		this._score_board_page_elements = this._loaded_data["score_board_page_elements"];
	} else {
		GSLog.Info("Initialising data");
		this._score_table               = scoreTableCreate();
		this._score_board_page          = GSStoryPage.New(GSCompany.COMPANY_INVALID, "Highscore");
		this._score_board_page_elements.append(
			GSStoryPage.NewElement(
				this._score_board_page,
				GSStoryPage.SPET_TEXT,
				0,
				GSText(GSText.STR_SCOREBOARD_TITLE)
			)
		);
	}

	this._loaded_data = null; // the loaded data is not needed after initialisation.
	this._init_done   = true;
}

/*
 * This method handles incoming events from OpenTTD.
 */
function KillCount::HandleEvents()
{
	if(GSEventController.IsEventWaiting()) {
		GSLog.Info("Waiting for events");
		local ev = GSEventController.GetNextEvent();
		if (ev == null) return;

		local ev_type = ev.GetEventType();
		switch (ev_type) {
			// Company events
			case GSEvent.ET_COMPANY_BANKRUPT: {
				// When a company goes bankrupt it stays on thescoreboard as retired
				local company_id = GSEventCompanyBankrupt.Convert(ev).GetCompanyID();
				this._score_table = scoreTableRetireCompany(this._score_table, company_id);
				this.UpdateScoreBoard();
				break;
			}
			case GSEvent.ET_COMPANY_MERGER: {
				// When a company is bought the buyer gets its scores
				local merge = GSEventCompanyMerger.Convert(event);
				local buyer = merge.GetNewCompanyID();
				local seller = merge.GetOldCompanyID();
				this._score_table = scoreTableMergeCompanies(this._score_table, buyer, seller);
				this.UpdateScoreBoard();
				break;
			}
			case GSEvent.ET_COMPANY_NEW: {
				local company_id = GSEventCompanyNew.Convert(ev).GetCompanyID();
				this._score_table = scoreTableAddCompany(this._score_table, company_id);
				this.UpdateScoreBoard();
				break;
			}
			case GSEvent.ET_COMPANY_RENAMED: {
				local event = GSEventCompanyRenamed.Convert(ev);
				local company_id = event.GetCompanyID();
				local new_name = GSCompany.GetName(company_id);
				this._score_table = scoreTableRenameCompany(this._score_table, company_id, new_name);
				this.UpdateScoreBoard();
				break;
			}
			// Vehicle crash
			case GSEvent.ET_VEHICLE_CRASHED: {
				local crash_event = GSEventVehicleCrashed.Convert(ev);
				local owner_id = crash_event.GetVehicleOwner();
				local victim_count = crash_event.GetVictims();
				this._score_table = scoreTableIncreaseScore(this._score_table, owner_id, victim_count, crash_event.GetCrashReason())
				this.UpdateScoreBoard();
				break;
			}
		}
	}
}

function KillCount::UpdateScoreBoard()
{
	// Clear the scoreboard first (all but the title)
	local element_list_size = this._score_board_page_elements.len();
	for (local i = 1; i < element_list_size; i++)
	{
		GSStoryPage.RemoveElement(this._score_board_page_elements.pop());
	}

	// Rebuild it
	foreach (i, company in scoreTableGetScoreTableAsList(this._score_table))
	{
		local active     = company.active? "": "[inactive] ";
		local rank       = i+1;
		local percentage = this._score_table.total_victims == 0? 0: company.victims_total * 100 / this._score_table.total_victims;
		local textobj    = GSText(
			GSText.STR_SCORE_COMPANY_TOTAL,
			rank,
			active,
			company.name,
			company.victims_total,
			company.victims_train,
			company.victims_road,
			company.victims_plane,
			company.victims_flood,
			percentage
		);
		this._score_board_page_elements.append(
			GSStoryPage.NewElement(
				this._score_board_page,
				GSStoryPage.SPET_TEXT,
				0,
				textobj
			)
		);
	}
	this._score_board_page_elements.append(
		GSStoryPage.NewElement(
			this._score_board_page,
			GSStoryPage.SPET_TEXT,
			0,
			GSText(
				GSText.STR_SCOREBOARD_TOTAL,
				this._score_table.total_victims
			)
		)
	);

}

/*
 * This method is called by OpenTTD when an (auto)-save occurs. You should
 * return a table which can contain nested tables, arrays of integers,
 * strings and booleans. Null values can also be stored. Class instances and
 * floating point values cannot be stored by OpenTTD.
 */
function KillCount::Save()
{
	GSLog.Info("Saving data to savegame");

	// In case (auto-)save happens before we have initialized all data,
	// save the raw _loaded_data if available or an empty table.
	if (!this._init_done) {
		return this._loaded_data != null ? this._loaded_data : {};
	}

	return {
		score_table               = this._score_table
		score_board_page          = this._score_board_page
		score_board_page_elements = this._score_board_page_elements
	};
}

/*
 * When a game is loaded, OpenTTD will call this method and pass you the
 * table that you sent to OpenTTD in Save().
 */
function KillCount::Load(version, tbl)
{
	GSLog.Info("Loading data from savegame made with version " + version + " of the game script");

	// Store a copy of the table from the save game
	// but do not process the loaded data yet. Wait with that to Init
	// so that OpenTTD doesn't kick us for taking too long to load.
	this._loaded_data = {}
   	foreach(key, val in tbl) {
		this._loaded_data.rawset(key, val);
	}
}
